﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/********************************************************
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 * 
 ********************************************************/


using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using EffiProzDB;

namespace System.Data.EffiProzDB
{
    public sealed class EffiProzDBTransaction : DbTransaction
    {
        /// <summary>
        /// The connection to which this transaction is bound
        /// </summary>
        internal EffiProzDBConnection _cnn;
        internal long _version; // Matches the version of the connection
        private IsolationLevel _isolationLevel;
        private string _savePoint = null;

        private static long savePointCounter = 0;
        private int _transactionLevel = -1;

        /// <summary>
        /// Constructs the transaction object, binding it to the supplied connection
        /// </summary>
        /// <param name="connection">The connection to open a transaction on</param>

        internal EffiProzDBTransaction(EffiProzDBConnection connection, IsolationLevel isolationLevel)
        {
            if (isolationLevel != IsolationLevel.ReadUncommitted)
                throw new NotSupportedException("IsolationLevel");

            _cnn = connection;
            _version = _cnn._version;
            this._isolationLevel = IsolationLevel;

            _transactionLevel = ++_cnn._transactionLevel;
            _savePoint = getNewSavePointName();
            try
            {
                Result req;               
                req = Result.newSetSavepointRequest(_savePoint);
                try
                {
                    _cnn.sessionProxy.execute(req);
                }
                catch (HsqlException e)
                {
                    EffiProzDBException.throwError(e);
                }

            }
            catch (EffiProzDBException)
            {
                _cnn._transactionLevel--;
                _cnn = null;
                _transactionLevel = -1;
                _savePoint = null;
                throw;
            }
        }

        private void ReleaseSavePoint(EffiProzDBConnection cnn)
        {
            //release save point
        
            Result req;
            req = Result.newReleaseSavepointRequest(_savePoint);
            try
            {
                Result result = cnn.sessionProxy.execute(req);

                if (result.isError())
                {
                    EffiProzDBException.throwError(result);
                }
            }
            catch (HsqlException e)
            {
                EffiProzDBException.throwError(e);
            }
        }

        public bool PrepareCommit()
        {
            if(!IsValid(false))
                return false;

            Result req = Result.newPrepareCommitRequest();

            try
            {
                Result result = _cnn.sessionProxy.execute(req);

                if (result.isError())
                {
                    EffiProzDBException.throwError(result);
                }
            }
            catch (HsqlException )
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Commits the current transaction.
        /// </summary>
        public override void Commit()
        {
            IsValid(true);
            
            //release save point
            if (_cnn._transactionLevel >= _transactionLevel)
            {


                if (_cnn._transactionLevel - 1 == 0) // Commit top level Tx
                {
                    Result req = Result.newCommitRequest();

                    try
                    {
                        Result result = _cnn.sessionProxy.execute(req);

                        if (result.isError())
                        {
                            EffiProzDBException.throwError(result);
                        }
                    }
                    catch (HsqlException e)
                    {
                        EffiProzDBException.throwError(e);
                    }
                }
                else
                {
                    ReleaseSavePoint(_cnn); // just release the save point
                }

                _cnn._transactionLevel = _transactionLevel;
            }
       
            _cnn = null;
            _savePoint = null;
            _transactionLevel = -1;
        }

        /// <summary>
        /// Returns the underlying connection to which this transaction applies.
        /// </summary>
        public new EffiProzDBConnection Connection
        {
            get { return _cnn; }
        }

        /// <summary>
        /// Forwards to the local Connection property
        /// </summary>
        protected override DbConnection DbConnection
        {
            get { return Connection; }
        }

        private string getNewSavePointName()
        {
            lock (typeof(EffiProzDBTransaction))
            {
                return "SavaPoint" + savePointCounter++;
            }
        }

        /// <summary>
        /// Disposes the transaction.  If it is currently active, any changes are rolled back.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (IsValid(false))
                {
                    IssueRollback();
                }
            }
            base.Dispose(disposing);
        }

        /// <summary>
        /// Gets the isolation level of the transaction.  EffiProzDB only supports Serializable transactions.
        /// </summary>
        public override IsolationLevel IsolationLevel
        {
            get { return _isolationLevel; }
        }

        /// <summary>
        /// Rolls back the active transaction.
        /// </summary>
        public override void Rollback()
        {
            IsValid(true);
            IssueRollback();
        }

        internal void IssueRollback()
        {
            EffiProzDBConnection cnn = Interlocked.Exchange(ref _cnn, null);
            Result req;          

            if(cnn._transactionLevel >= _transactionLevel){

                if (cnn != null)
                {
                    if (cnn._transactionLevel >= _transactionLevel)
                    {

                        req = Result.newRollbackToSavepointRequest(_savePoint);

                        try
                        {
                            Result result = cnn.sessionProxy.execute(req);

                            if (result.isError())
                            {
                                EffiProzDBException.throwError(result);
                            }
                        }
                        catch (HsqlException e)
                        {
                            EffiProzDBException.throwError(e);
                        }
                    }
                    else // just release the savepoint
                    {
                        ReleaseSavePoint(cnn);
                    }

                    cnn._transactionLevel = _transactionLevel;
                }                
            
                _transactionLevel = -1;
                _savePoint = null;
            }
        }

        internal bool IsValid(bool throwError)
        {
            if (_cnn == null)
            {
                if (throwError == true) throw new ArgumentNullException("No connection associated with this transaction");
                else return false;
            }

            if (_cnn._version != _version)
            {
                if (throwError == true) throw new EffiProzDBException("The connection was closed and re-opened, changes were already rolled back");
                else return false;
            }
            if (_cnn.State != ConnectionState.Open)
            {
                if (throwError == true) throw new EffiProzDBException("Connection was closed");
                else return false;
            }

            if (_cnn.sessionProxy.isAutoCommit())// fake transactins on autocommit
                return true;

            if (_cnn._transactionLevel == 0 || String.IsNullOrEmpty(_savePoint) ||
                _transactionLevel == -1)
            {
                _cnn._transactionLevel = 0; // Make sure the transaction level is reset before returning
                if (throwError == true) throw new EffiProzDBException("No transaction is active on this connection");
                else return false;
            }

            return true;
        }
    }
}
